package pwd.allen.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.*;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.annotation.Order;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.util.Properties;

/**
 * 数据库配置
 *
 * {@link org.springframework.transaction.annotation.Transactional}注解
 *  属性：
 *      propagation：指定事务传播机制，默认 Transaction
 *
 * @author lenovo
 * @create 2020-01-28 22:27
 **/
@EnableTransactionManagement//开启基于注解的声明式事务，可以代替xml的<tx:*>配置1
@PropertySource(value = {"classpath:/db.properties"}, encoding = "UTF-8")
@Configuration
@ImportResource("classpath:/db-init.xml")
@ComponentScan({"pwd.allen.dao", "pwd.allen.exception"})
@Order
public class DBConfig {

    @Value("${jdbc.url}")
    private String jdbcUrl;
    @Value("${jdbc.password}")
    private String password;

    /**
     * 该配置类引入了一些需要提前实例化的配置（例如事务处理器之类的），所以该配置类也会被提前实例化，此时spring还未注册有EmbeddedValueResolver（默认是在finishBeanFactoryInitialization阶段才会设置默认的EmbeddedValueResolver），@Value解析不了
     * 处理方法是static方式声明PropertySourcesPlaceholderConfigurer这个bean，能够在beanFactory初始化后就注册一个EmbeddedValueResolver
     * 加上static的话，不需要实例化当前Config即可获取PropertySourcesPlaceholderConfigurer
     *
     * 注册PropertySourcesPlaceholderConfigurer，用于支持解析bean definition和@Value中的${}占位符
     * xml声明方式：<context:property-placeholder>
     * 原理
     *  PropertySourcesPlaceholderConfigurer是BeanFactoryPostProcessor，容器启动时会调用其postProcessBeanFactory，添加EmbeddedValueResolver到spring中；
     *  bean实例化时属性通过AbstractBeanFactory.resolveEmbeddedValue方法使用注册的EmbeddedValueResolver进行解析
     *
     * 加上static的话，不需要实例化当前Config即可获取PropertySourcesPlaceholderConfigurer
     * static方式获取bean代码见 {@link org.springframework.beans.factory.support.ConstructorResolver#instantiateUsingFactoryMethod(String, RootBeanDefinition, Object[])}
     *
     * TODO static是否是必要的
     * @return
     */
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    /**
     * @Value从配置文件中解析url
     * @see org.springframework.beans.factory.support.AbstractBeanFactory#resolveEmbeddedValue
     * @return
     * @throws Exception
     */
    @Bean(initMethod = "init")
    public DruidDataSource dataSource() throws Exception {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUsername("root");
        dataSource.setPassword(password);
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl(jdbcUrl);
        dataSource.setLoginTimeout(5);
        // 配置监控统计拦截的filters，去掉后监控界面sql无法统计，'wall'用于防火墙；filter别名可以查看：druid-filter.properties
        dataSource.setFilters("stat,wall,slf4j");
        Properties properties = new Properties();
        // 通过connectProperties属性来打开mergeSql功能；慢SQL记录（单位毫秒）
        properties.setProperty("druid.stat.slowSqlMillis", "1000");
        dataSource.setConnectProperties(properties);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) throws Exception {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        return jdbcTemplate;
    }

    /**
     * NamedParameterJdbcTemplate类是线程安全的
     * 该类的最佳使用方式不是每次操作的时候实例化一个新的NamedParameterJdbcTemplate，而是针对每个DataSource只配置一个NamedParameterJdbcTemplate实例
     */
    @Bean
    public NamedParameterJdbcTemplate namedParameterJdbcTemplate(JdbcTemplate jdbcTemplate) {
        return new NamedParameterJdbcTemplate(jdbcTemplate);
    }

    @Bean
    public PlatformTransactionManager dataSourceTransactionManager(DataSource dataSource) throws Exception {
        return new DataSourceTransactionManager(dataSource);
    }

    /**
     * 异常转义器的后置处理器：会自动查找所有的异常转义器(实现 PersistenceexceptionTranslator接口的
     * bean),并且拦截所有标记为@ Repository注解的bean,通过代理来拦截异常,然后通过 Persisten
     * ceexceptionTranslator将DAO层异常转义后的异常抛出。
     * @return
     */
    @Bean
    public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor() {
        return new PersistenceExceptionTranslationPostProcessor();
    }
}
